package com.ruoyi.system.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Sets;
import com.ruoyi.common.config.MinIoProperties;
import com.ruoyi.common.minio.MyMinioClient;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.domain.File;
import com.ruoyi.system.domain.dto.FileChunkDTO;
import com.ruoyi.system.domain.enums.IsFinish;
import com.ruoyi.system.domain.vo.FileChunkVO;
import com.ruoyi.system.mapper.SysFileMapper;
import com.ruoyi.system.service.UploadService;
import io.minio.ListPartsResponse;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author : weixu
 * @date : 2024/7/12 14:42
 */
@Service
@Slf4j
public class UploadServiceImpl implements UploadService {

    @Autowired
    private MyMinioClient myMinioClient;
    @Autowired
    private MinIoProperties minIoProperties;

    @Autowired
    private SysFileMapper fileMapper;

    /**
     * 检查文件是否存在，如果存在则跳过该文件的上传，如果不存在，返回需要上传的分片集合
     *
     * @param chunkDTO
     * @return
     */
    @Override
    public FileChunkVO checkChunkExist(FileChunkDTO chunkDTO) {
        String fileMd5 = chunkDTO.getIdentifier();
        String fileName = chunkDTO.getFilename();
        //总大小
        Long fileSize = chunkDTO.getTotalSize();
        Long chunkSize = chunkDTO.getChunkSize();
        //总分片
        Integer chunkNum = chunkDTO.getTotalChunks();
        String bucketName = getBucketName();
        String objectName = DateUtil.today() + "/" + IdUtil.randomUUID() + "." + fileName.substring(fileName.lastIndexOf(".") + 1);
        //已上传的列表
        Set<Integer> uploaded = Sets.newConcurrentHashSet();
        boolean skipUpload = false;
        //根据文件唯一标识md5判断之前是否上传过此文件
        File file = fileMapper.selectFileByMd5(fileMd5);
        if (ObjectUtil.isNull(file)) {
            //文件未上传 创建任务进行上传
            String uploadId = myMinioClient.getUploadId(bucketName, objectName);
            //存储数据库
            file = File.builder()
                    .uploadId(uploadId)
                    .fileMd5(fileMd5)
                    .fileName(fileName)
                    .fileExtension(FileUtil.getSuffix(fileName))
                    .bucketName(bucketName)
                    .objectName(objectName)
                    .fileSize(fileSize)
                    .chunkSize(chunkSize)
                    .chunkNum(chunkNum)
                    .createBy(SecurityUtils.getUsername())
                    .createTime(new Date())
                    .build();
            fileMapper.insertSysFile(file);
        } else {
            if (file.isFinish()) {
                //已上传
                skipUpload = true;
            } else {
                //断点续传
                boolean isExist = myMinioClient.doesObjectExist(bucketName, objectName);
                if (!isExist) {
                    // 先查询已上传的分片数据
                    ListPartsResponse listPartsResponse = myMinioClient.listMultipart(bucketName, file.getObjectName(), file.getUploadId());
                    uploaded = listPartsResponse.result().partList().stream().map(Part::partNumber).collect(Collectors.toSet());
                }
            }
        }
        return FileChunkVO.builder()
                .skipUpload(skipUpload)
                .uploaded(uploaded)
                .objectName(objectName)
                .uploadId(file.getUploadId())
                .build();
    }

    /**
     * 上传分片
     *
     * @param chunkDTO
     */
    @Override
    public void uploadChunk(FileChunkDTO chunkDTO) {
        try (InputStream inputStream = chunkDTO.getFile().getInputStream()) {
                myMinioClient.UploadPart(getBucketName(), chunkDTO.getObjectName(), inputStream, chunkDTO.getCurrentChunkSize(), chunkDTO.getUploadId(), chunkDTO.getChunkNumber());
        } catch (Exception e) {
            log.error("上传分块失败", e);
            throw new RuntimeException("上传分块失败", e);
        }
    }

    @Override
    public boolean merge(String md5) {
        File file = fileMapper.selectFileByMd5(md5);
        try {
            String bucketName = file.getBucketName();
            String uploadId = file.getUploadId();
            String objectName = file.getObjectName();
            //查询分片
            ListPartsResponse partResult = myMinioClient.listMultipart(bucketName, objectName, uploadId);
            List<Part> partList = partResult.result().partList();
            //组装分片
            Part[] parts = partList.stream()
                    .map(part -> new Part(part.partNumber(), part.etag()))
                    .toArray(Part[]::new);
            myMinioClient.completeMultipartUpload(bucketName, objectName, uploadId, parts);
            //更新文件状态
            file.setIsFinish(IsFinish.FINISH.getCode());
            fileMapper.updateSysFile(file);
            return true;
        } catch (Exception e) {
            log.error("合并文件{}失败", JSON.toJSONString(file));
            throw new RuntimeException("合并文件失败", e);
        }
    }

    /**
     * 获取桶名
     *
     * @return
     */
    private String getBucketName() {
        return minIoProperties.getBucketName();
    }

}
